<template>
	<div class="chat-container">
		<div style="width: 100%; height: 100%; display: flex; background: var(--el-bg-color); overflow: hidden;">
			<div style="height: 100%; flex: 1; max-width: 1200px; margin: 0 auto; box-sizing: border-box; display: flex; flex-direction: column; position: relative; padding: 0 24px;">
				<!-- 页面标题 -->
				<div class="page-title">
					<h1>智能对话助手</h1>
				</div>
				<!-- 消息列表 -->
				<div class="chat-message-area">
					<Bubble.List :items="bubbleItems" :roles="roles" style="padding: 12px 20px; height: 100%; overflow-y: auto" :listGapVertical="10" />
				</div>
				<!-- 底部固定区域 -->
				<div class="chat-input-area">
					<!-- 检索方式选择区 -->
					<div class="model-select-area">
						<span class="select-label">检索模式:</span>
						<el-select v-model="retrievalType" placeholder="检索方式" style="width: 150px; margin-right: 8px" @change="handleRetrievalTypeChange">
							<el-option v-for="item in retrievalOptions" :key="item.type" :label="item.name" :value="item.type">
								<span>{{ item.name }}</span>
								<el-tooltip :content="item.description" placement="top" effect="light">
									<el-icon class="el-icon--right" style="margin-left: 4px"><info-filled /></el-icon>
								</el-tooltip>
							</el-option>
						</el-select>

						<!-- 提问建议 -->
						<div v-if="showSuggestions" class="question-suggestions">
							<div class="suggestion-list">
								<div v-for="(item, index) in questionSuggestions.slice(0, 3)" :key="index" class="suggestion-item" @click="useSuggestion(item)">
									{{ item }}
								</div>
							</div>
						</div>
					</div>
					<!-- 发送框 -->
					<Sender
						v-model="content"
						:loading="loading"
						style="box-shadow: var(--el-box-shadow-light)"
						@cancel="onStop"
						:value="content"
						@change="(val) => content = val"
						@submit="inputEnter"
					/>
				</div>
			</div>
		</div>
		<!-- 返回底部按钮 -->
		<el-button v-show="isShowToBottom" type="primary" circle class="bottom-btn" @click="backBottom">
			<el-icon>
				<ArrowDown />
			</el-icon>
		</el-button>
	</div>
</template>

<script setup lang="tsx">
import { Bubble, Sender } from 'ant-design-x-vue';
import { computed, h, nextTick, ref, VNode, onMounted } from 'vue';
import { chatStream, SSEEvent, getRetrievalTypes } from '@/api/rag';
import { ArrowDown, InfoFilled } from '@element-plus/icons-vue';
import { SyncOutlined } from '@ant-design/icons-vue';
import markdownit from 'markdown-it';
import { ElMessage } from 'element-plus';

// markdown解析器实例
const md = markdownit({ html: true, breaks: true });

// 创建Markdown渲染函数
const renderMarkdown = (content: string) => {
	// 使用Typography组件包装，提供更好的文本支持
	return h('div', { 
		class: 'markdown-content',
		innerHTML: md.render(content || '') 
	});
};

// 定义聊天消息类型
interface ChatMessage {
	content: string;
	role: 'user' | 'assistant';
	datetime: string;
	sources?: Array<{
		text: string;
		source?: string;
		type?: string;
	}>;
}

// 自定义气泡项类型
interface CustomBubbleItem {
	key: string;
	role: string;
	content: string;
	loading?: boolean;
	ellipsis?: boolean;
	extra?: {
		content: () => VNode;
	};
}

// 状态管理
const loading = ref(false);
const isStreamLoad = ref(false);
const isShowToBottom = ref(false);
const fetchCancel = ref(null);
const chatList = ref<ChatMessage[]>([]);
const content = ref('');
// 添加用户主动取消标志
const userCancelled = ref(false);

// 检索方式相关
const retrievalType = ref('AUTO');
const retrievalOptions = ref([
	{
		type: 'AUTO',
		name: '自动决定',
		description: '系统会根据您的问题自动选择最合适的检索方式',
	},
]);

// 会话角色配置
const roles = {
	ai: {
		placement: 'start' as const,
		typing: { step: 5, interval: 20 },
		avatar: {
			src: 'https://tdesign.gtimg.com/site/chat-avatar.png',
		},
		styles: {
			content: {
				borderRadius: '16px',
			},
		},
		messageRender: (content) => h('div', { 
			class: 'markdown-content',
			innerHTML: md.render(content || '')
		}),
		loadingRender: () => h('div', { class: 'loading-container' }, [
			h('div', { class: 'loading-spinner' }, [statusIconMap.value[loadingStatus.value]]),
			h('div', { class: 'loading-text' }, loadingStatusText.value),
		]),
	},
	user: {
		placement: 'end' as const,
		variant: 'shadow' as const,
		avatar: {
			src: 'https://tdesign.gtimg.com/site/avatar.jpg',
		},
		messageRender: (content) => h('div', { 
			class: 'markdown-content',
			innerHTML: md.render(content || '')
		}),
	},
};

// 状态文本，用于显示当前 SSE 状态
const loadingStatusText = ref('连接中...');
// 当前加载状态类型
const loadingStatus = ref('connecting');

// 状态映射
const statusIconMap = computed(() => {
	const map: Record<string, any> = {
		'connecting': h(SyncOutlined, { spin: true }),
		'retrieving': h(SyncOutlined, { spin: true }),
		'generating': h(SyncOutlined, { spin: true }),
		'completed': h(SyncOutlined, { spin: false }),
		'error': h(SyncOutlined, { spin: false })
	};
	return map;
});

// 更新加载状态类型
const updateLoadingStatus = (status: string) => {
	if (status.includes('召回') || status.includes('检索')) {
		loadingStatus.value = 'retrieving';
	} else if (status.includes('生成')) {
		loadingStatus.value = 'generating';
	} else if (status.includes('完成')) {
		loadingStatus.value = 'completed';
	} else if (status.includes('错误') || status.includes('失败')) {
		loadingStatus.value = 'error';
	} else {
		loadingStatus.value = 'connecting';
	}
};

// 将聊天记录转换为气泡列表项
const bubbleItems = computed(() => {
	if (chatList.value.length === 0) {
		return [];
	}

	return chatList.value.map((item, index) => {
		const bubbleItem: CustomBubbleItem = {
			key: index.toString(),
			role: item.role === 'user' ? 'user' : 'ai',
			content: item.content || '',
			// 只有当前最后一条消息且为空时才显示加载状态
			loading: item.role === 'assistant' && !item.content && loading.value && index === chatList.value.length - 1,
			ellipsis: false,
		};

		// 处理引用信息
		if (item.sources && item.sources.length > 0) {
			bubbleItem.extra = {
				content: () => (
					<div class='source-container'>
						<div class='source-title'>参考信息：</div>
						{item.sources.map((source, sourceIndex) => (
							<div class='source-item' key={sourceIndex}>
								<div class='source-text'>{source.text}</div>
								{source.source && <div class='source-meta'>文件来源: {source.source}</div>}
								{source.type && <div class='source-meta'>来源类型: {source.type}</div>}
							</div>
						))}
					</div>
				),
			};
		}

		return bubbleItem;
	});
});

// 获取检索类型列表
const fetchRetrievalTypes = async () => {
	try {
		const res = await getRetrievalTypes();
		if (res.data) {
			retrievalOptions.value = res.data;
		}
	} catch (error) {
		console.error('获取检索类型失败:', error);
		ElMessage.error('获取检索类型失败');
	}
};

// 处理检索类型变更
const handleRetrievalTypeChange = (value: string) => {
	retrievalType.value = value;
	console.log('检索方式已变更为:', value);
};

// 滚动到底部
const backBottom = () => {
	const chatContainer = document.querySelector('.chat-message-area .ant-x-bubble-list');
	if (chatContainer) {
		chatContainer.scrollTo({
			top: chatContainer.scrollHeight,
			behavior: 'smooth',
		});
	}
};

// 处理聊天窗口滚动
const handleChatScroll = (e: Event) => {
	const element = e.target as HTMLElement;
	isShowToBottom.value = element.scrollTop < element.scrollHeight - element.clientHeight - 100;
};

// 提问建议
const questionSuggestions = ref([
	'计算机网络包含哪些知识点？',
	'数据结构学习之前需要先学哪些课程？',
	'人工智能的基础理论有哪些？',
	'前端开发需要掌握哪些技术栈？',
	'如何学习分布式系统？'
]);

// 是否显示提问建议
const showSuggestions = computed(() => {
	// 只有一条消息(欢迎消息)时显示提问建议
	return chatList.value.length <= 1;
});

// 使用提问建议
const useSuggestion = (question: string) => {
	content.value = question;
	inputEnter(question);
};

// 清空聊天记录
const clearConfirm = () => {
	chatList.value = [];
	// 添加默认欢迎消息
	chatList.value.push({
		content: '你好我是人工智能助手，有什么可以帮助你的吗？',
		role: 'assistant',
		datetime: new Date().toLocaleString(),
	});
};

// 停止生成回答
const onStop = () => {
	if (fetchCancel.value) {
		// 设置用户主动取消标志
		userCancelled.value = true;
		// 中止请求
		fetchCancel.value.abort();
		// 重置状态
		loading.value = false;
		isStreamLoad.value = false;

		// 如果最后一条消息是空的，设置一个占位符内容，防止再次进入加载状态
		const lastMessage = chatList.value[chatList.value.length - 1];
		if (lastMessage && lastMessage.role === 'assistant' && !lastMessage.content.trim()) {
			lastMessage.content = '已取消生成';
		}
	}
};

// 发送消息
const inputEnter = async (message: string) => {
	if (isStreamLoad.value || !message) return;

	// 重置用户取消标志
	userCancelled.value = false;

	// 添加用户消息
	chatList.value.push({
		content: message,
		role: 'user',
		datetime: new Date().toLocaleString(),
	});

	// 添加AI消息占位
	chatList.value.push({
		content: '',
		role: 'assistant',
		datetime: new Date().toLocaleString(),
	});

	// 清空输入内容
	content.value = '';

	try {
		loading.value = true;
		isStreamLoad.value = true;
		// 更新初始状态文本
		loadingStatusText.value = '连接中...';

		// 创建流式请求
		fetchCancel.value = chatStream({
			message,
			// 使用选择的检索方式
			retrievalType: retrievalType.value,
			// 处理流式数据
			success: (event: SSEEvent) => {
				if (event.type === 'open') {
					loadingStatusText.value = '连接已建立';
				} else if (event.type === 'status') {
					// 更新加载状态文本
					loadingStatusText.value = event.data;
					// 更新加载状态类型
					updateLoadingStatus(event.data);
				} else if (event.type === 'token') {
					// 处理token事件
					const tokenData = event.data;

					// 获取当前助手回复内容
					const currentAssistantMessage = chatList.value[chatList.value.length - 1];

					// 特殊情况：如果收到的是只包含换行的token
					if (tokenData === '\n' || tokenData === '\r\n' || tokenData === '\\n') {
						currentAssistantMessage.content += '\n';
					}
					// 特殊情况：处理 "data:" 后面跟数字的情况（往往表示列表编号）
					else if (/^\d+$/.test(tokenData.trim())) {
						// 如果前一个字符不是换行符，先添加换行
						if (currentAssistantMessage.content &&
							!currentAssistantMessage.content.endsWith('\n')) {
							currentAssistantMessage.content += '\n';
						}
						currentAssistantMessage.content += tokenData;
					}
					// 普通token内容
					else {
						currentAssistantMessage.content += tokenData;
					}

					// 获取消息容器
					const chatContainer = document.querySelector('.chat-message-area .ant-x-bubble-list');
					if (chatContainer) {
						// 检查滚动位置
						const isAtBottom = 
							Math.abs((chatContainer.scrollHeight - chatContainer.scrollTop) - 
							chatContainer.clientHeight) < 30;
						
						// 如果用户已经滚动到底部或接近底部，保持滚动
						if (isAtBottom) {
							nextTick(() => {
								chatContainer.scrollTop = chatContainer.scrollHeight;
							});
						}
					}
				} else if (event.type === 'error') {
					console.error('流式请求出错:', event.data);
					chatList.value[chatList.value.length - 1].content += '\n\n[出错: ' + event.data + ']';
					loading.value = false;
				} else if (event.type === 'complete' || event.type === 'finish') {
					// 完成事件
					loading.value = false;
					isStreamLoad.value = false;
				}
			},
			// 处理请求失败
			fail: () => {
				console.error('流式请求失败');
				// 只有在非用户主动取消时才替换消息内容
				if (!userCancelled.value) {
					chatList.value[chatList.value.length - 1].content = '抱歉，请求失败，请稍后重试。';
				}
				loading.value = false;
				isStreamLoad.value = false;
			},
			// 处理请求完成
			complete: (success: boolean, message?: string) => {
				if (!success && !userCancelled.value) {
					console.error('流式请求结束出错:', message);
					// 只有在非用户主动取消且消息为空时才添加错误信息
					if (!chatList.value[chatList.value.length - 1].content) {
						chatList.value[chatList.value.length - 1].content = '抱歉，发生了错误，请稍后重试。';
					}
				}
				loading.value = false;
				isStreamLoad.value = false;
			},
		});
	} catch (error) {
		loading.value = false;
		isStreamLoad.value = false;
		console.error('流式请求异常:', error);
		// 只有在非用户主动取消时才替换消息内容
		if (!userCancelled.value) {
			chatList.value[chatList.value.length - 1].content = '抱歉，发生了错误，请稍后重试。';
		}
	}
};

// 监听滚动事件
const setupScrollListener = () => {
	nextTick(() => {
		const chatContainer = document.querySelector('.chat-message-area .ant-x-bubble-list');
		if (chatContainer) {
			chatContainer.addEventListener('scroll', handleChatScroll);
		}
	});
};

// 组件挂载
onMounted(() => {
	// 设置滚动监听
	setupScrollListener();
	// 获取检索类型列表
	fetchRetrievalTypes();
	// 添加默认欢迎消息
	chatList.value.push({
		content: '你好我是人工智能助手，有什么可以帮助你的吗？',
		role: 'assistant',
		datetime: new Date().toLocaleString(),
	});
});
</script>

<style lang="scss">
.chat-container {
	position: relative;
	height: 100%;
	width: 100%;

	.page-title {
		text-align: center;
		padding: 16px 0;
		border-bottom: none;
		background-color: var(--el-bg-color);
		z-index: 5;

		h1 {
			margin: 0;
			font-size: 20px;
			color: var(--el-text-color-primary);
		}
	}

	.chat-message-area {
		height: 100%;
		width: 100%;
		overflow: hidden;
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		padding-top: 70px; /* 为顶部标题预留空间 */
		padding-bottom: 130px; /* 为底部输入区域预留空间 */
	}

	.chat-input-area {
		position: absolute;
		bottom: 0;
		left: 0;
		right: 0;
		padding: 20px;
		background: var(--el-bg-color);
		border-top: none;
		z-index: 10;
	}

	.model-select-area {
		display: flex;
		align-items: center;
		margin-bottom: 10px;
		flex-wrap: wrap;
	}

	.select-label {
		font-size: 14px;
		color: var(--el-text-color-regular);
		margin-right: 8px;
	}

	.question-suggestions {
		margin-left: 16px;
		flex: 1;
	}

	.suggestion-list {
		display: flex;
		justify-content: flex-start;
		gap: 10px;
	}

	.suggestion-item {
		min-width: 120px;
		max-width: none;
		width: auto;
		padding: 6px 12px;
		border-radius: 16px;
		background-color: var(--el-fill-color-light);
		color: var(--el-text-color-regular);
		cursor: pointer;
		transition: all 0.2s ease;
		text-align: center;
		font-size: 14px;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
		border: 1px solid var(--el-border-color-lighter);
		position: relative;

		&:hover {
			background-color: var(--el-color-primary-light-9);
			color: var(--el-color-primary);
			border-color: var(--el-color-primary-light-5);
			white-space: normal;
			word-break: break-word;
			z-index: 10;
		}
	}

	.is-active {
		border-color: var(--el-color-primary) !important;
		background: var(--el-color-primary-light-9) !important;
		color: var(--el-color-primary) !important;
	}

	.bottom-btn {
		position: absolute;
		left: 50%;
		transform: translateX(-50%);
		bottom: 150px; /* 调整向上位置，避免与输入框重叠 */
		z-index: 10;
	}

	.loading-container {
		display: flex;
		align-items: center;
		padding: 8px 12px;
		background-color: var(--el-fill-color-light);
		border-radius: 8px;
		box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
	}

	.loading-spinner {
		margin-right: 10px;
		color: var(--el-color-primary);
	}

	.loading-text {
		font-size: 14px;
		color: var(--el-text-color-secondary);
		animation: pulse 1.5s infinite;
	}

	@keyframes pulse {
		0% { opacity: 0.6; }
		50% { opacity: 1; }
		100% { opacity: 0.6; }
	}

	.source-container {
		margin-top: 12px;
		padding: 8px 12px;
		background-color: var(--el-fill-color-light);
		border-radius: 6px;
		font-size: 14px;
	}

	.markdown-content {
		width: 100%;
		overflow-wrap: break-word;
	}

	.markdown-content pre {
		background-color: rgba(0, 0, 0, 0.05);
		border-radius: 3px;
		padding: 8px;
		overflow-x: auto;
		margin: 0 0 8px;
	}

	.markdown-content p {
		margin: 0 0 8px;
		line-height: 1.5;
	}
	
	/* 最后一个段落不要底部边距 */
	.markdown-content p:last-child {
		margin-bottom: 0;
	}

	.markdown-content ul, .markdown-content ol {
		padding-left: 20px;
		margin: 0 0 8px;
	}
	
	.markdown-content ul:last-child, .markdown-content ol:last-child {
		margin-bottom: 0;
	}
	
	.markdown-content table {
		border-collapse: collapse;
		width: 100%;
		margin: 8px 0;
	}
	
	.markdown-content table:last-child {
		margin-bottom: 0;
	}

	.markdown-content blockquote {
		border-left: 4px solid var(--el-border-color);
		padding-left: 10px;
		margin: 8px 0;
		color: var(--el-text-color-secondary);
	}

	.markdown-content blockquote:last-child {
		margin-bottom: 0;
	}

	.source-title {
		font-weight: bold;
		margin-bottom: 8px;
	}

	.source-item {
		margin-bottom: 8px;
		padding-bottom: 8px;
		border-bottom: 1px dashed var(--el-border-color-lighter);

		&:last-child {
			border-bottom: none;
			margin-bottom: 0;
			padding-bottom: 0;
		}
	}

	.source-text {
		margin-bottom: 4px;
		color: var(--el-text-color-regular);
	}

	.source-meta {
		font-size: 12px;
		color: var(--el-text-color-secondary);
	}

	.markdown-content blockquote:last-child {
		margin-bottom: 0;
	}
	
	.markdown-content code {
		background-color: rgba(0, 0, 0, 0.05);
		padding: 2px 4px;
		border-radius: 3px;
	}

	.markdown-content th, .markdown-content td {
		border: 1px solid var(--el-border-color-lighter);
		padding: 8px;
		text-align: left;
	}
}
</style>
